package cn.blnp.net.flowable.boot.localize.dynamic.jump.base;

import cn.blnp.net.flowable.boot.utils.ListUtils;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.impl.runtime.ChangeActivityStateBuilderImpl;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ChangeActivityStateBuilder;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ExecutionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>流程动态跳转基础与常见场景</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/6/17 15:13
 */
@Slf4j
@SpringBootTest
public class DynamicJumpFlowTest {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private IdentityService identityService;
    @Resource
    private TaskService taskService;

    SimplePropertyPreFilter taskFilter = new SimplePropertyPreFilter(Task.class, "id",
            "name", "executionId", "taskDefinitionKey", "assignee", "owner");
    SimplePropertyPreFilter executionFilter = new SimplePropertyPreFilter(Execution.class,
            "id", "parentId", "processInstanceId", "activityId", "isScope");


    /**
     *  <h1>底层接口的支持</h1>
     *  <p>
     *      核心接口：{@link ChangeActivityStateBuilder}
     *   <ol>
     *       <li>
     *           <b>moveExecutionToActivityId()：</b>将执行实例（executionId）从当前节点移动到指定的节点（activityId），通常用于流程实例的跳转和回退等场景；
     *       </li>
     *       <li>
     *           <b>moveExecutionsToSingleActivityId()：</b>将一组执行实例（executionIds）移动到指定的节点（activityId），通常用于将多个并行执行实例合并到一个节点，
     *           或者将多个执行实例移动到一个新的节点中；
     *       </li>
     *       <li>
     *           <b>moveSingleExecutionToActivityIds()：</b>将单个执行实例（executionId）移动到指定的一个或多个节点（activityIds），通常用于流程实例的跳转和回退等场景；
     *       </li>
     *       <li>
     *           <b>moveActivityIdTo()：</b>将流程实例从一个当前节点（currentActivityId）移动到指定的另一个节点（newActivityId），该方法用于流程中的节点的移动和转移，
     *           以便更好地控制流程的执行顺序和流转路径，常用于流程实例的跳转和回退等场景；
     *       </li>
     *       <li>
     *           <b>moveActivityIdsToSingleActivityId()：</b>将流程实例从多个当前所在节点（currentActivityIds）移动到一个单独的节点（newActivityId）；
     *       </li>
     *       <li>
     *           <b>moveSingleActivityIdToActivityIds()：</b>将流程实例从单个当前所在节点（currentActivityId）移动到多个节点（newActivityIds）；
     *       </li>
     *       <li>
     *           <b>moveActivityIdToParentActivityId()：</b>将调用活动所引用的子流程实例中的一个当前节点（currentActivityId）移动到主流程中的一个节点（newActivityId）；
     *       </li>
     *   </ol>
     *   &emsp;&emsp;除此之外，ChangeActivityStateBuilder的实现类ChangeActivityStateBuilderImpl中还额外提供了另外几个改变流程活动状态的方法：
     *   <ol>
     *       <li>
     *           <b>moveActivityIdsToParentActivityId()：</b>将调用活动所引用的子流程实例中的多个当前节点（currentActivityIds）移动到主流程中的一个节点（newActivityId），
     *           当移动到的新节点为用户任务时，分别设置它们的办理人和所属者为newAssigneeId、newOwnerId；
     *       </li>
     *       <li>
     *           <b>moveActivityIdsToSubProcessInstanceActivityId()：</b>将主流程中多个当前节点（activityIds）移动到调用活动（callActivityId）所引用的子流程指定流程定义版本
     *           （callActivitySubProcessVersion）中的一个节点（newActivityId），当移动到的新节点为用户任务时，分别设置它们的办理人和所属者为 newAssigneeId、newOwnerId。
     *       </li>
     *       <li>
     *           <b>moveSingleActivityIdToParentActivityIds()：</b>将调用活动所引用的子流程实例中的单个当前节点（currentActivityId）移动到主流程中的多个节点（newActivityIds）
     *       </li>
     *       <li>
     *           <b>moveSingleActivityIdToSubProcessInstanceActivityIds()：</b>将主流程中单个当前节点（currentActivityId）移动到调用活动（callActivityId）所引用的子流程指定
     *           流程定义版本（callActivitySubProcessVersion）中的多个节点（newActivityIds）。
     *       </li>
     *   </ol>
     *  </p>
     **/
    @Test
    public void test() {

    }


    /**
     *  <h1>动态跳转基础案例</h1>
     *  <p>
     *     本案例演示的是两个节点间的跳转是最基础、最常见的跳转场景。在流程定义中，定义了4个用户任务节点，其中firstNode和thirdNode为普通用户任务，secondNode为
     *   并行多实例任务，fourthNode为串行多实例任务。
     *      <ul>
     *          <li><b>流程定义文件：</b>{@link flow/dynamicJumpProcess.bpmn20.xml}</li>
     *          <li>
     *              <b>流程预览图：</b></br>
     *              <img src="https://cdn4.winhlb.com/2025/06/18/6852375bece99.png" >
     *          </li>
     *      </ul>
     *  </p>
     **/
    @Test
    public void test1() {
        /**
         *  结果分析：从执行结果可以看出，流程都完成了动态跳转，包括普通用户任务和多实例用户任务的跳出和跳入，实现的效果跟画线连接相同。当然，把用户任务换为其他类型的流程节点也是可以的。
         *  在这个例子中，多实例用户任务跳转采用的是moveActivityIdTo()方法。多实例用户任务的跳转也可以采用moveExecutionToActivityId()方法，此时传入的执行实例为多实例的根执行实例，
         *  以从第4个节点跳转到第2个节点为例，代码如下：
         *  <pre>
         *      {@code
         *          //查询第4个节点的用户任务
         *          Task fourthTask = taskService.createTaskQuery()
         *              .processInstanceId(procInst.getId())
         *              .taskDefinitionKey("fourthNode").singleResult();
         *          //查询用户任务的执行实例
         *          Execution execution = runtimeService.createExecutionQuery()
         *              .executionId(fourthTask.getExecutionId()).singleResult();
         *          //查询多实例用户任务的根执行实例
         *          Execution multiInstanceRootExecution = runtimeService.createExecutionQuery()
         *              .executionId(execution.getParentId()).singleResult();
         *          //从第4个节点跳转到第2个节点
         *          runtimeService.createChangeActivityStateBuilder()
         *              .moveExecutionToActivityId(multiInstanceRootExecution.getId(), "secondNode")
         *              .changeState();
         *      }
         *  </pre>
         **/
        Deployment deploy = repositoryService.createDeployment()
                .addClasspathResource("flow/dynamicJumpProcess.bpmn20.xml")
                .name("动态跳转基础案例")
                .deploy();
        //流程定义信息
        ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义ID：{}", processDefinition.getId());

        //初始化流程变量
        List<String> assigneeList1 = ListUtils.of("安琪拉", "妲己");
        List<String> assigneeList2 = ListUtils.of("胡桃", "丝柯克");
        Map<String, Object> variables = MapUtil.builder(new HashMap<String, Object>())
                .put("assigneeList1", assigneeList1)
                .put("assigneeList2", assigneeList2)
                .build();

        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(processDefinition.getId(), variables);
        log.info("流程实例ID：{}", procInst.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());
        ExecutionQuery executionQuery = runtimeService.createExecutionQuery()
                .processInstanceId(procInst.getId());

        //此时查询第一个用户任务
        Task firstTask = taskQuery.processInstanceId(procInst.getId()).singleResult();
        List<Execution> executionList = executionQuery.list();
        log.info("第一次跳转前，当前任务为：{}", JSON.toJSONString(firstTask, taskFilter));
        log.info("当前执行实例有：{}", JSON.toJSONString(executionList, executionFilter));

        //输出打印当前流程定义的用户任务节点信息
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
        List<FlowElement> flowElementList = (List<FlowElement>) bpmnModel.getMainProcess().getFlowElements();
        int num = 1;
        log.info("流程定义的任务节点有：");
        for (FlowElement flowElement : flowElementList) {
            if (flowElement instanceof UserTask) {
                log.info("{}、节点：{}（{}）",num, flowElement.getName(), flowElement.getId());
                num++;
            }
        }

        //此时从第1个节点跳转到第4个节点
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(procInst.getId())
                .moveExecutionToActivityId(firstTask.getExecutionId(), "fourthNode")
                .changeState();
        log.info("第一次跳转后，当前任务为：{}", JSON.toJSONString(taskQuery.list(), taskFilter));
        log.info("当前执行实例有：{}", JSON.toJSONString(executionQuery.list(), executionFilter));

        //再次从第4个节点跳转到第2个节点
        runtimeService.createChangeActivityStateBuilder()
                .processInstanceId(procInst.getId())
                .moveActivityIdTo("fourthNode", "secondNode")
                .changeState();
        log.info("第二次跳转后，当前任务为：{}",
                JSON.toJSONString(taskQuery.list(), taskFilter));
        log.info("当前执行实例有：{}", JSON.toJSONString(executionQuery.list(), executionFilter));

        //最后在从第2个节点跳转到第3个节点
        ((ChangeActivityStateBuilderImpl) runtimeService
                .createChangeActivityStateBuilder())
                .moveActivityIdTo("secondNode", "thirdNode", "大乔", "")
                .processInstanceId(procInst.getId())
                .changeState();
        log.info("第三次跳转后，当前任务为：{}", JSON.toJSONString(taskQuery.list(), taskFilter));
        log.info("当前执行实例有：{}", JSON.toJSONString(executionQuery.list(), executionFilter));

        /**
         *  演示结果：
         *  2025-06-18 14:03:10.936  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 流程定义ID：dynamicJumpProcess:1:205013
         * 2025-06-18 14:03:10.982  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 流程实例ID：205014
         * 2025-06-18 14:03:11.297  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 第一次跳转前，当前任务为：{"executionId":"205021","id":"205025","name":"普通用户任务1","taskDefinitionKey":"firstNode"}
         * 2025-06-18 14:03:11.315  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 当前执行实例有：[{"id":"205014","isScope":true,"processInstanceId":"205014"},{"activityId":"firstNode","id":"205021","isScope":false,"parentId":"205014","processInstanceId":"205014"}]
         * 2025-06-18 14:03:11.316  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 流程定义的任务节点有：
         * 2025-06-18 14:03:11.317  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 1、节点：普通用户任务1（firstNode）
         * 2025-06-18 14:03:11.317  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 2、节点：并行多实例任务（secondNode）
         * 2025-06-18 14:03:11.317  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 3、节点：普通用户任务2（thirdNode）
         * 2025-06-18 14:03:11.317  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 4、节点：串行多实例任务（fourthNode）
         * 2025-06-18 14:03:11.402  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 第一次跳转后，当前任务为：[{"assignee":"胡桃","executionId":"205028","id":"205035","name":"串行多实例任务","taskDefinitionKey":"fourthNode"}]
         * 2025-06-18 14:03:11.405  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 当前执行实例有：[{"id":"205014","isScope":true,"processInstanceId":"205014"},{"activityId":"fourthNode","id":"205027","isScope":false,"parentId":"205014","processInstanceId":"205014"},{"activityId":"fourthNode","id":"205028","isScope":false,"parentId":"205027","processInstanceId":"205014"}]
         * 2025-06-18 14:03:11.581  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 第二次跳转后，当前任务为：[{"assignee":"安琪拉","executionId":"205043","id":"205049","name":"并行多实例任务","taskDefinitionKey":"secondNode"},{"assignee":"妲己","executionId":"205044","id":"205054","name":"并行多实例任务","taskDefinitionKey":"secondNode"}]
         * 2025-06-18 14:03:11.584  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 当前执行实例有：[{"id":"205014","isScope":true,"processInstanceId":"205014"},{"activityId":"secondNode","id":"205039","isScope":false,"parentId":"205014","processInstanceId":"205014"},{"activityId":"secondNode","id":"205043","isScope":false,"parentId":"205039","processInstanceId":"205014"},{"activityId":"secondNode","id":"205044","isScope":false,"parentId":"205039","processInstanceId":"205014"}]
         * 2025-06-18 14:03:11.659  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 第三次跳转后，当前任务为：[{"assignee":"大乔","executionId":"205057","id":"205059","name":"普通用户任务2","taskDefinitionKey":"thirdNode"}]
         * 2025-06-18 14:03:11.663  INFO 15564 --- [           main] c.b.n.f.b.l.d.j.b.DynamicJumpFlowTest    : 当前执行实例有：[{"id":"205014","isScope":true,"processInstanceId":"205014"},{"activityId":"thirdNode","id":"205057","isScope":false,"parentId":"205014","processInstanceId":"205014"}]
         **/
    }
}
